package com.kai.auth.config;

import com.kai.admin.api.TokenApi;
import com.kai.auth.filter.TokenAuthenticationFilter;
import com.kai.auth.handler.AccessDeniedHandlerImpl;
import com.kai.auth.handler.AuthenticationEntryPointImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * Spring Security 配置适配器实现
 * </p>
 *
 * @author weijk
 * @since 2023/6/28
 */
@EnableWebSecurity
@EnableConfigurationProperties(SecurityProperties.class)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Resource
    private SecurityProperties securityProperties;

    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private AccessDeniedHandler accessDeniedHandler;

    @Resource
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Resource
    private TokenApi tokenApi;

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new AuthenticationEntryPointImpl();
    }

    @Bean
    public AccessDeniedHandler AccessDeniedHandlerImpl() {
        return new AccessDeniedHandlerImpl();
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(securityProperties.getWebStaticAllUrls());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        Map<HttpMethod, Set<String>> permitAllUrlsFromAnnotationMap = getPermitAllUrlsFromAnnotationMap();
        http
                // 取消跨站请求伪造防护
                .csrf().disable()
                // 基于token 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 设置 @PermitAll 无需认证
                .antMatchers(HttpMethod.GET, listConvertArray(permitAllUrlsFromAnnotationMap.get(HttpMethod.GET))).permitAll()
                .antMatchers(HttpMethod.POST, listConvertArray(permitAllUrlsFromAnnotationMap.get(HttpMethod.POST))).permitAll()
                .antMatchers(HttpMethod.PUT, listConvertArray(permitAllUrlsFromAnnotationMap.get(HttpMethod.PUT))).permitAll()
                .antMatchers(HttpMethod.DELETE, listConvertArray(permitAllUrlsFromAnnotationMap.get(HttpMethod.DELETE))).permitAll()
                .antMatchers(HttpMethod.PATCH, listConvertArray(permitAllUrlsFromAnnotationMap.get(HttpMethod.PATCH))).permitAll()
                // 基于 blog.security.permit-all-urls 无需认证
                .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()
                // 其他请求都要求认证
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint)
                .and()
                // 禁用缓存
                .headers().cacheControl().disable()
                .and()
                // 处理 Iframe 响应
                .headers().frameOptions().disable()
                .and()
                // 添加JWT登录授权过滤器
                .addFilterAfter(new TokenAuthenticationFilter(securityProperties, permitAllUrlsFromAnnotationMap, tokenApi), UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 获取 @PermitAll 注解的接口 path
     *
     * @return 接口 path map
     */
    private Map<HttpMethod, Set<String>> getPermitAllUrlsFromAnnotationMap() {
        return requestMappingHandlerMapping.getHandlerMethods().entrySet().stream()
                .filter(entry -> entry.getValue().hasMethodAnnotation(PermitAll.class)
                        && Objects.nonNull(entry.getKey().getPatternsCondition()))
                .map(entry -> {
                    Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
                    Set<String> urls = entry.getKey().getPatternsCondition().getPatterns();

                    return methods.stream().map(method -> {
                                Map<HttpMethod, Set<String>> map = new HashMap<>();
                                HttpMethod httpMethod = convertHttpMethod(method);
                                if (Objects.nonNull(httpMethod)) {
                                    map.put(httpMethod, urls);
                                }

                                return map;
                            }).flatMap(map -> map.entrySet().stream())
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

                }).flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (s1, s2) -> Stream.concat(s1.stream(), s2.stream()).collect(Collectors.toSet())));
    }

    /**
     * 转换 httpMethod
     */
    private HttpMethod convertHttpMethod(RequestMethod requestMethod) {
        switch (requestMethod) {
            case GET:
                return HttpMethod.GET;
            case PUT:
                return HttpMethod.PUT;
            case POST:
                return HttpMethod.POST;
            case DELETE:
                return HttpMethod.DELETE;
            case PATCH:
                return HttpMethod.PATCH;
            default:
                return null;
        }
    }

    /**
     * 集合转换数据
     */
    private String[] listConvertArray(Set<String> urls) {
        if (CollectionUtils.isEmpty(urls)) {
            return new String[]{};
        }

        return urls.toArray(new String[0]);
    }
}
